﻿#region Header

// MS codename Velocity Distributed Cache Extensions.
// (c) 2009

#endregion


#region Usings

using System;
using System.Diagnostics;

using Microsoft.Data.Caching;

#endregion


namespace Caching.Extensions.Core
{
    /// <summary>
    /// Cache item status.
    /// </summary>
    [Serializable]
    public enum CacheItemStatus
    {
        /// <summary>
        /// Item was not modified
        /// </summary>
        NonModified = 0,
        /// <summary>
        /// Item expired and removed from cache
        /// </summary>
        Expired,
        /// <summary>
        /// Item was updated since last cache retreival
        /// </summary>
        UpdatedOrNew
    }


    /// <summary>
    /// Result of query update operation for cache item 
    /// </summary>
    /// <remarks>See <seealso cref="DataCache.GetIfNewer(string,ref Microsoft.Data.Caching.DataCacheItemVersion)"/>
    /// to get more information on query update
    /// </remarks>
    public struct QueryUpdateResult : IEquatable<QueryUpdateResult>
    {
        /// <summary>
        /// Item status
        /// </summary>
        public CacheItemStatus Status { get; private set; }

        /// <summary>
        /// Item version
        /// </summary>
        /// <remarks>See <seealso cref="DataCacheItemVersion"/></remarks>
        public DataCacheItemVersion Version { get; private set; }

        /// <summary>
        /// Item value
        /// </summary>
        public object Value { get; private set; }


        /// <summary>
        /// ctor
        /// </summary>
        /// <param name="status">Item status</param>
        /// <param name="value">Item value</param>
        /// <param name="version">Version</param>
        public QueryUpdateResult(CacheItemStatus status, object value, DataCacheItemVersion version) : this()
        {
            Status = status;
            Value = value;
            Version = version;
        }


        /// <summary>
        /// Returns status corresponding to expired item
        /// </summary>
        /// <returns></returns>
        public static QueryUpdateResult Expired()
        {
            return new QueryUpdateResult(CacheItemStatus.Expired, null, null);
        }


        public bool Equals(QueryUpdateResult other)
        {
            return other.Status == Status && Equals(other.Version, Version) && Equals(other.Value, Value);
        }


        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (obj.GetType() != typeof (QueryUpdateResult)) return false;
            return Equals((QueryUpdateResult) obj);
        }


        public override int GetHashCode()
        {
            unchecked
            {
                int result = Status.GetHashCode();
                result = (result*397) ^ (Version != null ? Version.GetHashCode() : 0);
                result = (result*397) ^ (Value != null ? Value.GetHashCode() : 0);
                return result;
            }
        }


        public static bool operator ==(QueryUpdateResult left, QueryUpdateResult right)
        {
            return left.Equals(right);
        }


        public static bool operator !=(QueryUpdateResult left, QueryUpdateResult right)
        {
            return !left.Equals(right);
        }
    }


    /// <summary>
    /// MS Cache extension methods
    /// </summary>
    public static class CacheExtensions
    {
        /// <summary>
        /// Workaround for exception in OnRemove() if cache item is expired
        /// </summary>
        /// <param name="cache">Cache instance</param>
        /// <param name="key">Item key</param>
        /// <returns>True if item was sucessfully deleted, false if item not exists in cache</returns>
        public static bool SafeRemove(this DataCache cache, string key)
        {
            return SafeRemove(cache, key, string.Empty);
        }


        /// <summary>
        /// Workaround for exception in OnRemove() if cache item is expired
        /// </summary>
        /// <param name="cache">Cache instance</param>
        /// <param name="key">Item key</param>
        /// <param name="region">Region name</param>
        /// <returns>True if item was sucessfully deleted, false if item not exists in cache</returns>
        public static bool SafeRemove(this DataCache cache, string key, string region)
        {
            if (cache == null) throw new ArgumentNullException("cache");
            if (region == null) throw new ArgumentNullException("region");
            try
            {
                if (string.IsNullOrEmpty(region))
                {
                    cache.Remove(key);
                }
                else
                {
                    cache.Remove(key, region);
                }
                return true;
            }
            catch (DataCacheException ex)
            {
                if (ex.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)
                    return false;
                Debug.WriteLine("Error removing item " + ex, "MsCache");
                throw;
            }
        }


        /// <summary>
        /// OnRemove region without throwing exception
        /// </summary>
        /// <param name="cache">DataCase</param>
        /// <param name="regionName">Region name</param>
        /// <returns>true if region was exist and removed, false if region was not exist</returns>
        public static bool SafeRemoveRegion(this DataCache cache, string regionName)
        {
            if (cache == null) throw new ArgumentNullException("cache");
            if (string.IsNullOrEmpty(regionName)) throw new ArgumentNullException("regionName");

            try
            {
                cache.RemoveRegion(regionName);
                return true;
            }
            catch (DataCacheException ex)
            {
                if (ex.ErrorCode != DataCacheErrorCode.RegionDoesNotExist)
                    throw;
                return false;
            }
        }


        /// <summary>
        /// Create region
        /// </summary>
        /// <param name="cache">DataCache</param>
        /// <param name="regionName">Name of region to create</param>
        /// <param name="evictionOn">True, if objects can be evicted</param>
        /// <returns>True if region was created sucessfully, false if region already exists</returns>
        public static bool SafeCreateRegion(this DataCache cache, string regionName, bool evictionOn)
        {
            if (cache == null) throw new ArgumentNullException("cache");
            if (string.IsNullOrEmpty(regionName)) throw new ArgumentNullException("regionName");

            try
            {
                cache.CreateRegion(regionName, evictionOn);
                return true;
            }
            catch (DataCacheException ex)
            {
                if (ex.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
                    throw;
                return false;
            }
        }


        /// <summary>
        /// Query update for item by version
        /// </summary>
        /// <param name="cache">Cache</param>
        /// <param name="key">Cache ITem Key</param>
        /// <param name="region">Region</param>
        /// <param name="value">Cache Item</param>
        /// <param name="version">Cache Item Version</param>
        /// <returns>Query update result</returns>
        public static QueryUpdateResult QueryUpdate(this DataCache cache, string key, string region, object value,
            DataCacheItemVersion version)
        {
            if (cache == null) throw new ArgumentNullException("cache");
            if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");

            if (version == null)
            {
                return GetNewObject(cache, key, region);
            }

            try
            {
                object updatedObj = GetIfNewer(cache, key, ref version, region);

                return updatedObj != null
                    ?
                        new QueryUpdateResult(CacheItemStatus.UpdatedOrNew, updatedObj, version)
                    : new QueryUpdateResult(CacheItemStatus.NonModified, value, version);
            }
            catch (DataCacheException ex)
            {
                if (ex.ErrorCode == DataCacheErrorCode.KeyDoesNotExist)
                {
                    return QueryUpdateResult.Expired();
                }
                throw;
            }
        }


        private static object GetIfNewer(DataCache cache, string key, ref DataCacheItemVersion version, string region)
        {
            object updatedObj = string.IsNullOrEmpty(region)
                ? cache.GetIfNewer(key, ref version)
                : cache.GetIfNewer(key, ref version, region);
            return updatedObj;
        }


        private static QueryUpdateResult GetNewObject(DataCache cache, string key, string region)
        {
            DataCacheItem cacheItem = string.IsNullOrEmpty(region)
                ? cache.GetCacheItem(key)
                : cache.GetCacheItem(key, region);

            return cacheItem == null
                ?
                    QueryUpdateResult.Expired()
                : new QueryUpdateResult(CacheItemStatus.UpdatedOrNew, cacheItem.Value, cacheItem.Version);
        }


#if false
        #region Async calls (POC)
        
        private static readonly PutDelegate s_putDelegate = DoPut;


        // todo: test this out
        public delegate DataCacheItemVersion PutDelegate(DataCache cache, string key, string region, object value);


        private static DataCacheItemVersion DoPut(DataCache cache, string key, string region, object value)
        {
            return cache.Put(key, value, region);
        }


        public static IAsyncResult BeginPut(this DataCache cache, string key, string region, object value,
            AsyncCallback callback, object state)
        {
            return s_putDelegate.BeginInvoke(cache, key, region, value, callback, state);
        }


        public static DataCacheItemVersion EndPut(IAsyncResult ar)
        {
            return s_putDelegate.EndInvoke(ar);
        }

        #endregion Async calls (POC)
#endif
    }
}